import LoggerManager from "@/log/LoggerManager.ts";
import IndexDBAddDataError from "@/error/IndexDBDataError.ts";
import IndexDBOpenError from "@/error/IndexDBOpenError.ts";
import IndexDBConstraintError from "@/error/IndexDBConstraintError.ts";
import IndexDBIndexReplicationError from "@/error/IndexDBIndexReplicationError.ts";
import IndexDBQueryError from "@/error/IndexDBQueryError.ts";

/**
 * 添加数据类型
 */
export type AddData = Record<string, unknown>

/**
 * 更新数据类型
 */
export interface UpdateData extends AddData {
    /**
     * 更新数据的 id编号
     */
    id: string | number
}

/**
 * 数据库信息
 */
export interface DatabaseInfo {
    indexNames: string[] | DOMStringList,
    storeNames: string[] | DOMStringList,
}

/**
 * 数据更新的IDBRequest处理日志配置
 */
interface RequestHandleLog {
    /**
     * 处理成功打印的日志
     */
    success: string
    /**
     * 处理失败打印的日志
     */
    error: string
    /**
     * 事务处理完成的日志
     */
    tx_complete?: string
    /**
     * 事务处理阻塞的日志
     */
    tx_abort?: string
    /**
     * 事务处理出错的日志
     */
    tx_error?: string
}

const log = LoggerManager.getLogger("IndexDatabase");

/**
 * indexDB数据库类, 处理indexDB数据操作
 */
export default class IndexDatabase {
    private readonly dbName: string;
    // 版本号后续还需要更新, 所以不能是只读的
    private dbVersion: number;
    private readonly tableName: string;
    private isClose = false;
    private readonly database: Promise<IDBDatabase | null>

    /**
     * indexDB操作构造函数
     * @param dbName 需要操作的数据库名称
     * @param dbVersion 操作的数据库版本号
     * @param tableName 需要操作的存储器(数据表)名称
     * @param primaryKey 数据表的主键字段名称
     * @param searchIndex 数据的数据查找索引名字
     */
    constructor(dbName: string, dbVersion: number, tableName: string, primaryKey: string, searchIndex: string) {
        log.debug("Init IndexDatabase.")
        this.dbName = dbName;
        this.dbVersion = dbVersion
        this.tableName = tableName;
        this.database = this.init(dbName, dbVersion, tableName, primaryKey, searchIndex)
            .then(res => res)
            .catch((err: unknown) => {
                log.error("Initialize database error.\n", err as object);
                return null;
            })
    }

    /**
     * 初始化数据库
     */
    private async init(dbName: string, version: number, tableName: string, primaryKey: string, searchIndex: string): Promise<IDBDatabase> {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(dbName, version);
            // 数据库打开失败
            request.onerror = (event) => {
                const err = new IndexDBOpenError(dbName, version, (event.target as IDBRequest).error);
                log.error(err);
                reject(err);
            }
            // 数据库阻塞
            request.onblocked = function (event) {
                log.warn("The database is currently in use by another connection. Please try again later.", event)
                reject(new Error("The database is currently in use by another connection. Please try again later."));
            }
            // 数据库打开成功, 但是已经存在了
            request.onsuccess = (event) => {
                // 表示数据库升级, 数据库对象在onupgradeneeded装载
                //     log.debug(`The database failed to initialize, the database [${request.result.name}] already exists.`);
                log.tract("Database open success, Load database objects and set their status.");
                this.isClose = false;
                resolve((event.target as IDBOpenDBRequest).result);
            }
            // 数据库升级
            request.onupgradeneeded = (event) => {
                log.debug("Database is upgradeneeded ...");
                const db = (event.target as IDBOpenDBRequest).result;
                try {
                    const store = db.createObjectStore(tableName, {keyPath: primaryKey, autoIncrement: true});
                    const index = store.createIndex(searchIndex + "_index", searchIndex, {unique: false});
                    log.debug(`Create store [${tableName}], primaryKey: ${primaryKey}, searchIndex: ${searchIndex}.`, index);
                    this.isClose = false;
                    // resolve((event.target as IDBOpenDBRequest).result);
                } catch (e: unknown) {
                    log.debug("Database update error: ", e);
                    reject(new Error("Database update error."));
                }
            }
        });
    }

    public async add(data: AddData, update?: false): Promise<number>;
    public async add(data: UpdateData, update: true): Promise<number>;
    /**
     * 添加数据, 修改修改数据则令update=true
     * @param {object} data  - 需要添加的数据
     * @param {boolean} update - 是否更新数据, 如果为true, 则更新id相同的数据, 并且id必须不为空
     */
    public async add(data: AddData | UpdateData, update?: boolean): Promise<number> {
        log.tract(`Add or Update data to indexDB store name: [ ${this.tableName} ], updateFlag: [ ${update} ], data:`, data);
        try {
            const store = await this.getStore("readwrite");
            if (update) {
                const logs = {
                    success: `Add data to ${this.tableName} success.`,
                    error: `Add data to ${this.tableName} failure.`,
                }
                log.debug("Switch to the mode for updating data.")
                const target = await this.handlerIDBRequest(store.put(data), logs);
                return target.result as number;
            } else {
                const logs = {
                    success: `Add data to ${this.tableName} success.`,
                    error: `Add data to ${this.tableName} failure.`,
                }
                log.debug("Switch to the mode for adding data.")
                const target = await this.handlerIDBRequest(store.put(data), logs);
                return target.result as number;
            }
        } catch (e: unknown) {
            log.error("Data add ou update failure", e);
            throw new IndexDBAddDataError(data, (e as DOMException))
        }
    }

    /**
     * 根据数据主键查询数据信息
     */
    public async queryByPrimaryKey<T extends object>(primaryKey_: string | number): Promise<T> {
        log.tract(`Query data from primaryKey: ${primaryKey_} .`);
        const logs = {
            success: "Query data from primaryKey success.",
            error: "Query data from primaryKey failure.",
        }
        try {
            const store = await this.getStore("readonly");
            const target = await this.handlerIDBRequest(store.get(primaryKey_), logs);
            return target.result as T;
        } catch (e: unknown) {
            log.error(logs.error, e);
            throw new IndexDBQueryError(this.dbName, this.tableName, "", "", (e as DOMException));
        }
    }

    /**
     * 根据数据索引查找数据信息
     * @param {string} indexField 数据索引字段
     * @param {string} searchValue 索引字段的值
     * @return {Promise} 返回查询到的数据信息 1 条
     */
    public async queryByIndexOne<T extends object>(indexField: string, searchValue: string): Promise<T> {
        log.tract(`Query data from index,  indexField: ${indexField}, search: ${searchValue}`);
        const logs = {
            success: "Query data from index success.",
            error: "Query data from index failure.",
        }
        try {
            const store = await this.getStore("readonly");
            const index = store.index(indexField + "_index");
            const target = await this.handlerIDBRequest(index.get(searchValue), logs);
            return target.result as T;
        } catch (e: unknown) {
            log.error(logs.error, e);
            throw new IndexDBQueryError(this.dbName, this.tableName, "", "", (e as DOMException));
        }
    }

    /**
     * 根据数据索引查找数据信息
     * @param {string} indexField 数据索引字段
     * @param {string} searchValue 索引字段的值
     * @return {Promise<[]>} 返回查询到的数据信息多条
     */
    public async queryByIndexList<T extends object>(indexField: string, searchValue: string): Promise<T[]> {
        log.tract(`Query list data from index, indexField: ${indexField}, search: ${searchValue}`);
        const logs = {
            success: "Query list data from index success.",
            error: "Query list data from index failure.",
        }
        try {
            const store = await this.getStore("readonly");
            const index = store.index(indexField + "_index");
            const target = await this.handlerIDBRequest(index.getAll(IDBKeyRange.only(searchValue)), logs);
            return target.result as T[];
        } catch (e: unknown) {
            log.error(logs.error, e);
            throw new IndexDBQueryError(this.dbName, this.tableName, "", "", (e as DOMException));
        }
    }

    /**
     * 查找全部数据, 如果数据太多的话, 请使用 queryAllLimit 方法
     */
    public async queryAll<T extends object>(): Promise<T[]> {
        log.tract("Query all data.");
        const logs = {
            success: "Query all data success.",
            error: "Query all data failure.",
        }
        try {
            const store = await this.getStore("readonly");
            const target = await this.handlerIDBRequest(store.getAll(), logs);
            return target.result as T[];
        } catch (e: unknown) {
            log.error(logs.error, e);
            throw new IndexDBQueryError(this.dbName, this.tableName, "", "", (e as DOMException));
        }
    }

    /**
     * 根据主键删除数据
     * @param {number} primaryKey 数据库主键值
     */
    public async deleteByPrimaryKey(primaryKey: number): Promise<boolean> {
        log.tract("Delete data by primary key: ", primaryKey);
        try {
            const store = await this.getStore("readwrite");
            const deleteResult = store.delete(primaryKey);
            const logs = {
                success: `Delete data success, primaryKey: ${primaryKey}`,
                error: `Delete data failure, primaryKey: ${primaryKey}`,
            }
            await this.handlerIDBRequest(deleteResult, logs);
            return true;
        } catch (e: unknown) {
            log.error("Data delete failure", e);
            throw new IndexDBQueryError(this.dbName, this.tableName, "", "", (e as DOMException));
        }
    }

    /**
     * 根据数据库索引删除数据
     * @param {string} indexField 数据索引字段
     * @param {string} searchValue 索引字段的值
     * @param {boolean} more 是否删除多个值
     * */
    public async deleteByIndex(indexField: string, searchValue: string, more?: boolean): Promise<boolean> {
        log.tract(`Delete data by field index filed: [${indexField}], value: [${searchValue}], more: [${more}]`);
        const logs = {
            success: `Delete data by index success.`,
            error: `Delete data by index failure.`,
        }
        try {
            const store = await this.getStore("readwrite");
            const index = store.index(indexField + "_index");
            if (more) {
                log.debug("Delete multiple pieces of data.")
                const idRes = await this.handlerIDBRequest(index.getAllKeys(IDBKeyRange.only(searchValue)), logs);
                const _ids = idRes.result as number[];
                for (const id of _ids) {
                    log.debug("Delete data by primary key: ", id);
                    await this.deleteByPrimaryKey(id);
                }
                return true;
            }
            // 获取要删除数据的主键
            const idRes = await this.handlerIDBRequest(index.getKey(searchValue), logs);
            const _id = idRes.result as number | undefined;
            if (_id) {
                log.debug("Delete the primary key of the data :", _id);
                return await this.deleteByPrimaryKey(_id);
            } else {
                log.debug("No primary key to be deleted was found.")
                return false;
            }
        } catch (e: unknown) {
            log.error("Data delete by index failure.", e);
            throw e;
        }
    }

    /**
     * 处理数据更新的请求结果
     * @param request 请求结果对象
     * @param logs 请求处理日志
     */
    private async handlerIDBRequest(request: IDBRequest, logs: RequestHandleLog): Promise<IDBRequest> {
        return new Promise((resolve, reject) => {
            // 删除或者添加数据成功
            request.onsuccess = (event) => {
                const target = event.target as IDBRequest
                log.debug(logs.success);
                resolve(target);
            }
            // 删除或者添加失败
            request.onerror = (event) => {
                log.error(logs.error, event.target);
                reject(new Error(logs.error));
            }
            // 处理事务的回调
            if (request.transaction) {
                request.transaction.oncomplete = () => {
                    log.debug(logs.tx_complete ?? "Data update transaction complete.");
                }
                request.transaction.onabort = () => {
                    log.error(logs.tx_abort ?? "Data update transaction aborted.")
                }
                request.onerror = () => {
                    log.error(logs.tx_error ?? "Data update transaction error.")
                }
            }
        });
    }

    /**
     * 获取数据更新的 IDBObjectStore 对象
     * @param mode 操作模式
     */
    private async getStore(mode: "readonly" | "readwrite"): Promise<IDBObjectStore> {
        const db = await this.database;
        return new Promise<IDBObjectStore>((resolve, reject) => {
            if (this.isClose || !db) {
                log.warn("The database has not been initialized.")
                reject(new Error("The database has not been initialized."));
            } else {
                const tx = db.transaction([this.tableName], mode);
                const store = tx.objectStore(this.tableName);
                resolve(store);
            }
        });
    }

    /**
     * 显示数据库信息
     */
    public async showDatabaseInfo(): Promise<DatabaseInfo> {
        const db = await this.database;
        if (this.isClose || !db) {
            log.warn("The database has not been initialized.")
            throw new Error("The database has not been initialized.");
        }
        return new Promise((resolve, reject) => {
            const tx = db.transaction([this.tableName], 'readonly');
            const store = tx.objectStore(this.tableName);
            try {
                const data: DatabaseInfo = {
                    indexNames: store.indexNames,
                    storeNames: tx.objectStoreNames,
                }
                tx.commit();
                resolve(data);
            } catch (e: unknown) {
                log.error("Get database information failure", e)
                reject(new Error("Get database information failure"));
            }
        });
    }

    /**
     * 删除数据库索引
     * @param field 索引名字
     * @param newVersion 需要升级的版本号
     */
    public async deleteIndex(field: string, newVersion: number): Promise<boolean> {
        log.tract(`Delete index [ ${field} ], currentVersion: ${this.dbVersion}, newVersion: ${newVersion}`);
        await this.close();
        const request = indexedDB.open(this.dbName, newVersion);
        const store = await this.handlerOpenResult(request, newVersion, field);
        if (store.indexNames.contains(field + "_index")) {
            // 如果存在则不报错
            throw new IndexDBIndexReplicationError(this.dbName, this.tableName, this.dbVersion, field);
        } else {
            // 执行成功反回新建索引对象
            store.deleteIndex(field + "_index");
            return true;
        }
    }

    /**
     * 为书库创建索引, 创建索引后, 需要重重注册本类对象才可以继续使用
     * @param field 需要创建索引的字段
     * @param newVersion
     * @param {boolean} unique 是否唯一索引, 默认为false
     * @returns {Promise<number | false>} 创建索引成功返回索引版本号, 创建索引失败返回false
     */
    public async createIndex(field: string, newVersion: number, unique?: boolean): Promise<IDBIndex> {
        log.tract(`Create new field index, field: [ ${field} ], unique: [ ${unique} ], currentVersion: [ ${this.dbVersion} ], newVersion: [ ${newVersion} ]`);
        await this.close();
        const request = indexedDB.open(this.dbName, newVersion);
        const store = await this.handlerOpenResult(request, newVersion, field);
        if (store.indexNames.contains(field + "_index")) {
            // 如果存在则不报错
            throw new IndexDBIndexReplicationError(this.dbName, this.tableName, this.dbVersion, field);
        } else {
            // 执行成功反回新建索引对象
            return store.createIndex(field + "_index", field, {unique: unique ?? false});
        }
    }

    /**
     * 处理数据库升级的result
     * @param openResult
     * @param newVersion
     * @private
     */
    private async handlerOpenResult(openResult: IDBOpenDBRequest, newVersion: number, field: string): Promise<IDBObjectStore> {
        return new Promise((resolve, reject) => {
            openResult.onsuccess = (event) => {
                log.tract("Index create completed, close current database connect.");
                (event.target as IDBOpenDBRequest).result.close();
            }
            openResult.onerror = (event) => {
                log.error("Index create failure, the database upgrade failed. ", event);
                reject(new IndexDBConstraintError(this.dbName, this.tableName, newVersion, field, (event.target as IDBRequest).error));
            }
            openResult.onblocked = () => {
                log.error("Index create failure ,the database is being used by another connection.");
            }
            openResult.onupgradeneeded = (event) => {
                const tx = (event.target as IDBOpenDBRequest).transaction;
                if (tx === null) {
                    log.error("Index create failure, the transaction not found. ")
                    reject(new Error("Index create failure, the transaction not found."));
                    return;
                }
                tx.oncomplete = () => {
                    log.debug("Database update transaction completed.");
                }
                tx.onabort = () => {
                    log.error("Database update failure, the transaction aborted.");
                }
                tx.onerror = () => {
                    log.error("Database update failure, the transaction error.");
                }
                const store = tx.objectStore(this.tableName);
                resolve(store);
            }
        });
    }

    /**
     * 清空当前存储器的全部数据,但是不会删除存储器吗,清空后可以继续添加信息
     */
    public async clear(): Promise<boolean> {
        log.tract(`Clear store [ ${this.tableName} ].`)
        const db = await this.database;
        if (this.isClose || !db) {
            log.warn("The database has not been initialized.")
            return Promise.reject(new Error("The database has not been initialized."));
        }
        return new Promise((resolve, reject) => {
            const tx = db.transaction([this.tableName], "readwrite");
            const store = tx.objectStore(this.tableName);
            const clearResult = store.clear();
            clearResult.onsuccess = () => {
                log.debug(`Clear store [ ${this.tableName} ] success.`);
                resolve(true);
            }
            clearResult.onerror = () => {
                log.error(`Clear store [ ${this.tableName} ] error.`);
                resolve(false);
            }
            tx.onabort = () => {
                log.error(`Clear store [ ${this.tableName} ] transaction aborted.`)
                reject(new Error(`Clear store [ ${this.tableName} ] transaction aborted.`));
            }
            tx.onerror = () => {
                log.error(`Clear store [ ${this.tableName} ] transaction error.`)
                reject(new Error(`Clear store [ ${this.tableName} ] transaction error.`));
            }
            tx.oncomplete = () => {
                log.debug(`Clear store [ ${this.tableName} ] transaction complete.`)
            }
        });
    }

    /**
     * 关闭数据库连接
     */
    public async close() {
        if (this.isClose) {
            log.debug("Database is closed.")
            return;
        }
        await this.database.then(res => {
            if (res) {
                log.debug("Close database.")
                res.close();
                this.isClose = true;
            } else {
                log.warn("The database has not been initialized.")
            }
        });
    }

    /**
     * 删除数据库, 调用此方法之前, 请先调用close()方法关闭数据库连接
     * @param dbName
     */
    public static deleteDatabase(dbName: string) {
        return new Promise((resolve, reject) => {
            const request = indexedDB.deleteDatabase(dbName);
            request.onsuccess = function () {
                log.debug(`Database ${dbName} exec delete success.`);
                resolve(true);
            }
            request.onerror = function () {
                log.error(`Database ${dbName} exec delete error, unexplained reason.`);
                reject(new Error(`Database ${dbName} exec delete error, unexplained reason.`));
            }
            request.onblocked = function () {
                log.warn(`Database ${dbName} exec delete error, perform blocking, retry after closing all database connections.`);
                reject(new Error(`Database ${dbName} exec delete error, perform blocking, retry after closing all database connections.`));
            }
        });
    }
}